﻿using System.Data;
using Devonline.AspNetCore;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.Identity;

/// <summary>
/// 用户认证授权相关基础服务
/// 
/// 授权服务的设计思路: 查询出用户的所有身份和授权数据保存到缓存中, 每次请求时, 从缓存中再次读取并验证受否有权限
///     此方案: 设计简单, 但效率不高, 因为每次读写缓存, 查询, 需要序列化和反序列化的数据量过大, 性能损耗较大
/// 
/// 新授权服务的设计思路: 
/// 第一次查询出用户所有身份和授权数据, 保存到缓存中
/// 并且同时保存: 系统+用户+权限 构成的键值对到缓存中, 以及缓存顶级系统所有数据
/// 再次验证权限时, 再次使用 系统+用户+权限 来作为键读取缓存进行验证
///     此方案: 设计复杂, 但效率高, 对开发调试和授权配置更友好
/// </summary>
public class AuthorizationService
{
    private const string CONTROLLER = "controller";
    private const string ACTION = "action";
    private readonly HttpSetting _httpSetting;
    private readonly ILogger<AuthorizationService> _logger;
    private readonly IDistributedCache _cache;
    private readonly IdentityDbContext _context;
    private readonly HttpContext _httpContext;
    private readonly HttpRequest _request;
    private readonly RouteValueDictionary _routeValues;
    private readonly string _userName;
    private readonly bool _enableCache;
    private readonly DistributedCacheEntryOptions? _cacheEntryOptions;
    /// <summary>
    /// 用户上下文数据, 恒定数据, 用于确认用户是否完成了数据初始化, 键值为用户数据缓存的键
    /// </summary>
    private static readonly Dictionary<string, UserContext> _userContexts = [];
    /// <summary>
    /// 系统级资源, 恒定数据, 为: { 系统级资源编号: 资源 } 构成
    /// </summary>
    private static readonly Dictionary<string, ResourceViewModel> _systemResources = [];
    /// <summary>
    /// 所有资源的集合, 恒定数据, 为: 系统: { 资源地址: 资源 }} 的结构
    /// </summary>
    private static readonly Dictionary<string, Dictionary<string, ResourceViewModel>> _allResources = [];
    /// <summary>
    /// 所有系统级资源的集合, 恒定数据, 为: { 系统: 资源树 } 的结构
    /// </summary>
    private static readonly Dictionary<string, ResourceViewModel> _allResourceTrees = [];
    /// <summary>
    /// 定时器用于处理背景事务
    /// </summary>
    private static readonly System.Timers.Timer _timer = new();

    public AuthorizationService(
        HttpSetting httpSetting,
        ILogger<AuthorizationService> logger,
        IHttpContextAccessor httpContext,
        IDistributedCache cache,
        IdentityDbContext context
        )
    {
        ArgumentNullException.ThrowIfNull(httpContext.HttpContext);
        _httpSetting = httpSetting;
        _logger = logger;
        _httpContext = httpContext.HttpContext;
        _request = _httpContext.Request;
        _cache = cache;
        _context = context;
        _routeValues = _request.RouteValues;
        _userName = _httpContext.GetUserName();

        if (httpSetting.Cache is not null)
        {
            _enableCache = true;
            _cacheEntryOptions = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(httpSetting.Cache.ExpireTime) };
        }

        if (!_timer.Enabled)
        {
            //定时器工作于每晚凌晨2点
            _timer.Start(SaveAndClearUserContextsAsync, DateTime.Today.AddDays(AppSettings.UNIT_ONE).AddHours(AppSettings.UNIT_TWO));
        }
    }

    /// <summary>
    /// 授权判断
    /// </summary>
    /// <param name="userName">用户名</param>
    /// <param name="resource">资源</param>
    /// <returns></returns>
    public async Task<IActionResult> AuthorizeAsync(string? userName = default, string? resource = default)
    {
        userName ??= _httpContext.GetUserName();
        var userContext = await GetUserContextAsync(userName);
        if (userContext is null || userContext.User is null)
        {
            _logger.LogInformation($"user {userName ?? userContext?.UserName ?? AppSettings.USER_ANONYMOUS} can not get user access resources!");
            return new ForbidResult();
        }

        if (_httpSetting.EnableIdentityType && (userContext.Identities is null || userContext.Identities.Count == 0) && _httpSetting.UserInteraction is not null && _httpSetting.UserInteraction.IdentitySelect is not null && _request.Path != _httpSetting.UserInteraction.IdentitySelect)
        {
            var userInteractions = _httpSetting.UserInteraction.ToKeyValuePairs(false, false).Where(x => x.Key != nameof(_httpSetting.UserInteraction.IdentitySelect) && x.Key != nameof(_httpSetting.UserInteraction.Home) && x.Key != nameof(_httpSetting.UserInteraction.Error));
            if (userInteractions.Any(x => x.Value.ToString() == _request.Path))
            {
                //尚未做出身份选择时, 强制重定向到身份选择页面
                return new RedirectResult(_httpSetting.UserInteraction.IdentitySelect);
            }
        }

        var viewModel = GetPermissionResource(userContext, resource);
        if (viewModel is null)
        {
            _logger.LogInformation($"user {userContext.UserName} can not get permission to access resource!");
            return new ForbidResult();
        }

        //验证访问规则
        if (!await AuthorizeAccessRuleAsync(userContext, viewModel))
        {
            _logger.LogInformation($"user {userContext.UserName} not match the accress rules to access resource!");
            return new ForbidResult();
        }

        return new OkResult();
    }

    #region v1
    /// <summary>
    /// 获取用户上下文
    /// 上下文包含用户所有已分配身份可获取的资源列表及其树形结构
    /// 此阶段不判断 Condition 条件
    /// </summary>
    /// <param name="userName">用户名</param>
    /// <param name="force">是否强制获取</param>
    /// <returns></returns>
    public async Task<UserContext?> GetUserContextAsync(string? userName = default, bool force = false)
    {
        userName ??= _httpContext.GetUserName();
        UserContext? userContext = null;
        if (_userContexts.ContainsKey(userName))
        {
            _logger.LogInformation($"get {userName} user context from memory cache!");
            userContext = _userContexts[userName];
            if (userContext is not null && !force)
            {
                //记录最后一次访问时间
                userContext.AccessTime = DateTime.UtcNow;
                return userContext;
            }
        }

        _logger.LogInformation($"get {userName} user context from cache!");
        var userCacheKey = AppSettings.CACHE_USER + userName;
        userContext = await _cache.GetValueAsync<UserContext>(userCacheKey);
        if (userContext is not null)
        {
            _userContexts[userName] = userContext;
            if (!force)
            {
                //记录最后一次访问时间
                userContext.AccessTime = DateTime.UtcNow;
                return userContext;
            }
        }

        _logger.LogInformation($"get {userName} user context from database!");
        var user = await _context.Users.AsNoTracking().FirstOrDefaultAsync(x => x.State == DataState.Available && x.UserName == userName);

        //用户已登录,但未创建用户信息
        user ??= await _context.Users.AsNoTracking().FirstOrDefaultAsync(x => x.State == DataState.Available && x.Type == AuthorizeType.Anonymous);
        if (user is null)
        {
            return null;
        }

        //脱敏
        user.PasswordHash = null;
        user.PhoneNumber = user.PhoneNumber.Desensitize();

        //1. 获取用户直属组织身份
        GroupViewModel? groupViewModel = null;
        if (!string.IsNullOrWhiteSpace(user.GroupId))
        {
            _logger.LogInformation($"get {userName} user group from database!");
            var group = await _context.Groups.FindAsync(user.GroupId);
            if (group != null && group.State == DataState.Available)
            {
                groupViewModel = group.CopyTo<GroupViewModel>();
            }
        }

        //2. 获取用户级别身份
        LevelViewModel? levelViewModel = null;
        if (!string.IsNullOrWhiteSpace(user.LevelId))
        {
            _logger.LogInformation($"get {userName} user level from database!");
            var level = await _context.Levels.FindAsync(user.LevelId);
            if (level != null && level.State == DataState.Available)
            {
                levelViewModel = level.CopyTo<LevelViewModel>();
            }
        }

        //3. 获取身份选择数据
        Dictionary<IdentityType, string[]>? identities = null;
        if (_enableCache && _httpSetting.EnableIdentityType)
        {
            identities = await _cache.GetValueAsync<Dictionary<IdentityType, string[]>>(AppSettings.CACHE_USER_IDENTITY_TYPE + userName);
        }

        userContext = new UserContext
        {
            IsCurrentUser = userName == _userName,
            IsAuthenticated = _httpContext.User.Identity?.IsAuthenticated ?? false,
            User = user.CopyTo<UserViewModel>(),
            Group = groupViewModel,
            Level = levelViewModel,
            Roles = [],
            Groups = [],
            Resources = [],
            ResourceTrees = [],
            Identities = identities
        };

        //4. 获取用户角色身份
        var roleIds = await _context.UserRoles.AsNoTracking().Where(x => x.UserId.Equals(user.Id)).Select(x => x.RoleId).Distinct().ToListAsync();
        if (roleIds.IsNotNullOrEmpty())
        {
            _logger.LogInformation($"get {userName} user roles from database!");
            var roles = _context.Roles.AsNoTracking().Where(x => x.State == DataState.Available && roleIds.Contains(x.Id)).Select(x => new RoleViewModel
            {
                Id = x.Id,
                Alias = x.Alias,
                Image = x.Image,
                Description = x.Description,
                Name = x.Name!,
                NormalizedName = x.NormalizedName,
                State = x.State,
                Type = x.Type
            }).ToList();

            if (roles.IsNotNullOrEmpty())
            {
                userContext.Roles.AddRange(roles);
            }
        }

        //4.1 一旦登录, 默认给到认证用户的角色
        if (userContext.IsCurrentUser && userContext.IsAuthenticated || userContext.User.Type >= AuthorizeType.Authenticator)
        {
            var role = await _context.Roles.AsNoTracking().Where(x => x.State == DataState.Available && x.Type == AuthorizeType.Authenticator).Select(x => new RoleViewModel
            {
                Id = x.Id,
                Alias = x.Alias,
                Image = x.Image,
                Description = x.Description,
                Name = x.Name!,
                NormalizedName = x.NormalizedName,
                State = x.State,
                Type = x.Type
            }).FirstOrDefaultAsync();

            if (role is not null)
            {
                userContext.Roles.Add(role);
            }
        }

        //5. 获取用户组织身份
        var groupIds = await _context.UserGroups.AsNoTracking().Where(x => x.UserId != null && x.UserId.Equals(user.Id)).Select(x => x.GroupId).Distinct().ToListAsync();
        if (groupIds is not null && groupIds.Count != 0)
        {
            _logger.LogInformation($"get {userName} user groups from database!");
            var userGroups = new List<GroupViewModel>();
            foreach (var groupId in groupIds)
            {
                if (groupId is not null)
                {
                    var groups = await _context.GetParentsAsync<Group>(groupId);
                    if (groups.IsNotNullOrEmpty())
                    {
                        userGroups.AddRange(groups.CopyTo<List<GroupViewModel>>());
                    }
                }
            }

            if (userGroups.Count != 0)
            {
                userContext.Groups.AddRange(userGroups);
            }
        }

        //6. 获取用户访问规则及可访问资源
        await GetAllResources(userContext, force);
        var accessRules = await GetUserAccessRulesAsync(userContext);
        var resourceViewModels = new List<ResourceViewModel>();
        if (_allResourceTrees is not null && accessRules is not null)
        {
            foreach (var resource in _allResourceTrees.Values)
            {
                resourceViewModels.AddRange(GetAccessResources(resource, [], accessRules));
            }

            //此时的全局所有资源和用户资源不包括系统级资源
            var userResources = resourceViewModels.Where(x => x.SystemId != null).GroupBy(x => x.SystemId).ToDictionary(x => _systemResources[x.Key!].Content.ToLowerInvariant(), x => x.OrderBy(x => x.Code).ToDictionary(a => a.Content.ToLowerInvariant(), a => a));
            userContext.Resources.AddRange(userResources);

            var resourcesTree = new List<ResourceViewModel>();
            foreach (var resource in _systemResources.Values)
            {
                var node = resource.Copy();
                InitUserResourceTree(resourceViewModels, node);
                if (node.Children is not null && node.Children.Count != 0)
                {
                    resourcesTree.Add(node);
                }
            }

            var topResourcesTree = resourcesTree.Where(x => x.ParentId == null);
            foreach (var resource in topResourcesTree)
            {
                GetChildrenResources(resource, resourcesTree);
            }

            userContext.ResourceTrees.AddRange(topResourcesTree.ToDictionary(x => x.Content, x => x));
        }

        if (_enableCache)
        {
            //记录最后一次访问时间
            userContext.AccessTime = DateTime.UtcNow;
            await _cache.SetValueAsync(userCacheKey, userContext, _cacheEntryOptions!);
        }

        return userContext;
    }
    /// <summary>
    /// 返回用户是否有某个资源的访问权限, 判断数据来自缓存, 判断依据为资源编号或资源内容必须包含在用户可访问资源缓存列表中
    /// </summary>
    /// <param name="resource">资源编号或内容</param>
    /// <param name="userName">用户名</param>
    /// <returns></returns>
    public async Task<bool> UserHasPermissionAsync(string resource, string? userName = default)
    {
        if (string.IsNullOrWhiteSpace(resource))
        {
            return false;
        }

        var userContext = await GetUserContextAsync(userName);
        if (userContext is null || userContext.Resources is null)
        {
            return false;
        }

        resource = resource.ToLowerInvariant();
        var uri = new Uri(resource);
        var system = uri.Host;
        var path = uri.PathAndQuery.ToLowerInvariant();
        return GetMatchedResource(userContext, system, path) is not null;
    }
    /// <summary>
    /// 根据身份类型和编号获取对应的用户
    /// </summary>
    /// <param name="identityType">身份分配类型</param>
    /// <param name="identities">身份编号</param>
    public async Task<ICollection<User>?> GetIdentityUsers(IdentityType identityType = IdentityType.User, params string[] identities)
    {
        var queryable = _context.Users.AsNoTracking().Where(x => x.State == DataState.Available);
        if (identities.Length == 0)
        {
            return await queryable.ToListAsync();
        }

        return identityType switch
        {
            IdentityType.User => await queryable.Where(x => identities.Contains(x.Id) || identities.Contains(x.UserName)).ToListAsync(),
            IdentityType.Role => await queryable.Where(x => _context.UserRoles.AsNoTracking().Where(a => identities.Contains(a.RoleId)).Select(a => a.UserId).Contains(x.Id)).ToListAsync(),
            IdentityType.Group => await queryable.Where(x => _context.UserGroups.AsNoTracking().Where(a => identities.Contains(a.GroupId)).Select(a => a.UserId).Contains(x.Id)).ToListAsync(),
            IdentityType.Level => await queryable.Where(x => identities.Contains(x.LevelId)).ToListAsync(),
            IdentityType.System => await queryable.Where(x => x.Type == AuthorizeType.System && x.UserName == AppSettings.USER_SYSTEM).ToListAsync(),
            IdentityType.Anonymous => await queryable.Where(x => x.Type == AuthorizeType.Anonymous && x.UserName == AppSettings.USER_ANONYMOUS).ToListAsync(),
            _ => await queryable.ToListAsync(),
        };
    }
    /// <summary>
    /// 给当前用户自动添加角色, 适用于特殊情况下的设置角色, 只能设置内部角色类型更低的类型
    /// </summary>
    /// <param name="roleName">角色名, 角色不能是内部角色以上类型的角色</param>
    /// <returns></returns>
    public async Task<bool> AutoRoleAsync(string roleName)
    {
        var userName = _httpContext.GetUserName();
        _logger.LogInformation("user {user} will be auto assigned to role {role}", userName, roleName);
        var user = await _context.Users.FindAsync(_httpContext.GetUserId());
        if (user is null)
        {
            _logger.LogWarning($"当前用户 {userName} 不存在!");
            return false;
        }

        var role = await _context.Roles.FirstOrDefaultAsync(x => x.Name == roleName);
        if (role is null)
        {
            _logger.LogWarning($"当前角色 {roleName} 不存在!");
            return false;
        }

        //自动设定角色, 只能设置角色类型低于 内部(Internal) 角色的类型
        if (role.Type >= AuthorizeType.Internal)
        {
            _logger.LogWarning($"当前角色 {roleName} 不可进行自动设置!");
            return false;
        }

        var userRole = await _context.UserRoles.FirstOrDefaultAsync(x => x.UserId == user.Id && x.RoleId == role.Id);
        if (userRole is not null)
        {
            _logger.LogInformation($"当前用户 {userName} 已经是 {roleName} 角色了!");
            return true;
        }

        userRole = new UserRole { UserId = user.Id, RoleId = role.Id };
        await _context.UserRoles.AddAsync(userRole);
        var result = await _context.SaveChangesAsync();
        if (result > 0)
        {
            _logger.LogWarning($"当前用户 {userName} 已经成功设置 {roleName} 角色!");

            //刷新用户缓存
            await GetUserContextAsync(userName, true);
            return true;
        }

        _logger.LogWarning($"当前用户 {userName} 未能成功设置 {roleName} 角色!");
        return false;
    }
    #endregion

    #region v2
    /// <summary>
    /// 将用户上下文数据保存到缓存中
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    private async Task SaveAndClearUserContextsAsync()
    {
        _logger.LogDebug($"save expire user contexts to cache!");

        //查询过期的内存中的用户缓存
        var expireTime = DateTime.UtcNow.AddHours(-_httpSetting.Cache.ReserveTime);
        var expireUsers = _userContexts.Values.Where(x => x.AccessTime <= expireTime).Select(x => x.UserName);
        if (expireUsers.Any())
        {
            foreach (var userName in expireUsers)
            {
                await _cache.SetValueAsync(AppSettings.CACHE_USER + userName, _userContexts[userName]);
                _userContexts.Remove(userName);
            }

            _logger.LogInformation("save expire user contexts to cache complete!");
        }
        else
        {
            _logger.LogDebug("there has no expired user contexts save to cache!");
        }
    }
    /// <summary>
    /// 从用户 Http 请求的上下文获取缓存中的值
    /// </summary>
    /// <param name="userName"></param>
    /// <param name="resource"></param>
    /// <returns></returns>
    private async Task<string?> GetAuthorizedResourceAsync(string? userName = default, string? resource = default)
    {
        userName ??= _httpContext.GetUserName();
        var system = _request.Host.Value;
        resource ??= _request.Path;
        var key = GetResourceKey(userName, system, resource);
        return await _cache.GetStringAsync(key);
    }

    /// <summary>
    /// 获取当前资源对应的系统级资源, 保存的时候, 
    /// </summary>
    /// <param name="resource"></param>
    /// <returns></returns>
    private string GetSystemResourcePath(ResourceViewModel resource) => string.IsNullOrWhiteSpace(resource.SystemId) ? resource.Content : _systemResources[resource.SystemId].Content;
    /// <summary>
    /// 根据 用户:系统:资源 获取缓存的键
    /// </summary>
    /// <param name="userName">当前用户名</param>
    /// <param name="system">授权的系统</param>
    /// <param name="resource">授权的资源路径</param>
    /// <returns></returns>
    private string GetResourceKey(string userName, string system, string resource) => $"{userName}:{system}:{resource}".GetHashValue().ToHexString(char.MinValue);
    #endregion

    #region 内部方法
    /// <summary>
    /// 获取当前用户所有已分配的访问规则列表
    /// </summary>
    /// <param name="userContext">认证上下文</param>
    /// <returns></returns>
    private async Task<ICollection<AccessRuleViewModel>?> GetUserAccessRulesAsync(UserContext userContext)
    {
        var identities = new List<string>();
        if (userContext.UserId is not null)
        {
            identities.Add(userContext.UserId);
        }

        if (userContext.Group is not null)
        {
            identities.Add(userContext.Group.Id);
        }

        if (userContext.Level is not null)
        {
            identities.Add(userContext.Level.Id);
        }

        if (userContext.Roles is not null && userContext.Roles.Count != 0)
        {
            if (userContext.Identities?.Any(x => x.Key == IdentityType.Role) ?? false)
            {
                identities.AddRange(userContext.Roles.Where(x => userContext.Identities[IdentityType.Role].Contains(x.Name)).Select(x => x.Id));
            }
            else
            {
                identities.AddRange(userContext.Roles.Select(x => x.Id));
            }
        }

        if (userContext.Groups is not null && userContext.Groups.Count != 0)
        {
            if (userContext.Identities?.Any(x => x.Key == IdentityType.Role) ?? false)
            {
                identities.AddRange(userContext.Groups.Where(x => userContext.Identities[IdentityType.Group].Contains(x.Name)).Select(x => x.Id));
            }
            else
            {
                identities.AddRange(userContext.Groups.Select(x => x.Id));
            }
        }

        //获取用户系统身份
        if (userContext.IsSystem)
        {
            //系统中只能有一个 System 类型的用户的用户名叫: AppSettings.USER_SYSTEM 的
            var systemUser = await _context.Users.AsNoTracking().FirstOrDefaultAsync(x => x.State == DataState.Available && x.Type == AuthorizeType.System && x.UserName == AppSettings.USER_SYSTEM);
            if (systemUser is not null)
            {
                identities.Add(systemUser.Id);
            }
        }

        //获取匿名用户身份
        //系统中只能有一个 Anonymous 类型的用户, 且用户名叫: AppSettings.USER_ANONYMOUS 的
        var anonymousUser = await _context.Users.AsNoTracking().FirstOrDefaultAsync(x => x.State == DataState.Available && x.Type == AuthorizeType.Anonymous && x.UserName == AppSettings.USER_ANONYMOUS);
        if (anonymousUser is not null)
        {
            identities.Add(anonymousUser.Id);
        }

        //查询满足条件的所有访问规则
        _logger.LogInformation($"get {userContext.UserName} user access rules from database!");
        return await _context.AccessRules.AsNoTracking()
            .Where(x => x.State == DataState.Available && (!x.ExpireTime.HasValue || x.ExpireTime.Value >= DateTime.Now) && ((x.IdentityId != null && identities.Contains(x.IdentityId)) || x.IdentityType == IdentityType.All))
            .Select(x => new AccessRuleViewModel
            {
                Id = x.Id,
                AccessCount = x.AccessCount,
                Code = x.Code,
                Condition = x.Condition,
                ExpireTime = x.ExpireTime,
                IdentityId = x.IdentityId,
                IdentityType = x.IdentityType,
                IsAllow = x.IsAllow,
                Priority = x.Priority,
                ResourceId = x.ResourceId,
                State = x.State
            })
            .ToListAsync();
    }
    /// <summary>
    /// 获取全部的资源列表及其树形结构
    /// </summary>
    /// <param name="force">是否强制获取</param>
    /// <returns></returns>
    private async Task GetAllResources(UserContext userContext, bool force = false)
    {
        if (_allResources.Count <= 0 || force && _enableCache)
        {
            _allResources.Clear();
            _allResourceTrees.Clear();
            _systemResources.Clear();

            //全量资源不存在时, 先取缓存
            var cacheKey = AppSettings.CACHE_APPLICATION + "RESOURCES";
            var treeCacheKey = AppSettings.CACHE_APPLICATION + "RESOURCES_TREE";
            var systemCacheKey = AppSettings.CACHE_APPLICATION + "SYSTEM_RESOURCES";
            var resources = await _cache.GetValueAsync<Dictionary<string, Dictionary<string, ResourceViewModel>>>(cacheKey);
            var resourceTrees = await _cache.GetValueAsync<Dictionary<string, ResourceViewModel>>(treeCacheKey);
            var systemResources = await _cache.GetValueAsync<Dictionary<string, ResourceViewModel>>(systemCacheKey);

            if (resources?.Any() ?? false)
            {
                _allResources.AddRange(resources);
            }

            if (resourceTrees?.Any() ?? false)
            {
                _allResourceTrees.AddRange(resourceTrees);
            }

            if (systemResources?.Any() ?? false)
            {
                _systemResources.AddRange(systemResources);
            }

            //在从数据库查询
            if (force || resources is null || resourceTrees is null || systemResources is null)
            {
                _allResources.Clear();
                _allResourceTrees.Clear();
                _systemResources.Clear();

                var allResources = (await _context.Resources.AsNoTracking().Where(x => x.State == DataState.Available).OrderBy(x => x.Code).ToListAsync()).CopyTo<List<ResourceViewModel>>();
                systemResources = allResources.Where(x => x.SystemId == null).ToDictionary(x => x.Id, x => x);
                if (systemResources.Count > 0)
                {
                    _systemResources.AddRange(systemResources.Copy());
                    foreach (var resource in systemResources.Values)
                    {
                        var list = allResources.Where(x => x.SystemId == resource.Id);
                        _allResources.Add(resource.Content.ToLowerInvariant(), list.ToDictionary(x => x.Content.ToLowerInvariant(), x => x.CopyTo<ResourceViewModel>()));

                        GetChildrenResources(resource, list);
                        _allResourceTrees.Add(resource.Content.ToLowerInvariant(), resource);
                    }

                    if (_enableCache)
                    {
                        await _cache.SetValueAsync(cacheKey, _allResources, _cacheEntryOptions);
                        await _cache.SetValueAsync(treeCacheKey, _allResourceTrees, _cacheEntryOptions);
                        await _cache.SetValueAsync(systemCacheKey, _systemResources, _cacheEntryOptions);
                    }
                }
            }
        }
    }

    /// <summary>
    /// 递归获取并设置资源的子资源
    /// </summary>
    /// <param name="resource"></param>
    /// <param name="resources"></param>
    private static void GetChildrenResources(ResourceViewModel resource, IEnumerable<ResourceViewModel> resources)
    {
        var children = resources.Where(x => x.ParentId == resource.Id).OrderBy(x => x.Code).ToList();
        if (children is not null && children.Count != 0)
        {
            resource.Children = children;
            foreach (var child in children)
            {
                GetChildrenResources(child, resources);
            }
        }
    }
    /// <summary>
    /// 对权限树中的当前节点及其子节点设置权限
    /// </summary>
    /// <param name="resource">当前资源节点</param>
    /// <param name="accessRules">当前节点拥有的访问规则列表</param>
    /// <param name="fullAccessRules">当前用户获取到的所有访问规则列表</param>
    private static ICollection<ResourceViewModel> GetAccessResources(ResourceViewModel resource, List<AccessRuleViewModel> accessRules, ICollection<AccessRuleViewModel> fullAccessRules)
    {
        var resources = new List<ResourceViewModel>();

        //1. 先获取当前资源满足条件的访问规则
        var currentAccessRules = fullAccessRules.Where(x => x.ResourceId == resource.Id).UnionBy(accessRules, x => x.Id).OrderByDescending(x => x.Priority).ToList();

        //2. 如果当前资源有子资源
        if (resource.Children is not null && resource.Children.Count != 0)
        {
            //3. 且当前资源有权访问, 则递归设置子资源的权限
            foreach (var child in resource.Children)
            {
                resources.AddRange(GetAccessResources(child, currentAccessRules, fullAccessRules));
            }
        }

        //3. 按照规则优先级最高的一条设置当前资源的权限, 如果当前资源没有访问规则，则视为无权限; 或当前资源的子资源有访问权限, 则认为当前资源有访问权限
        resource.HasPermission = currentAccessRules.FirstOrDefault()?.IsAllow == AllowType.Allow;
        if (resource.HasPermission || resources.Count != 0)
        {
            resources.Add(new ResourceViewModel
            {
                Id = resource.Id,
                State = resource.State,
                Name = resource.Name,
                Code = resource.Code,
                Alias = resource.Alias,
                Type = resource.Type,
                Image = resource.Image,
                Title = resource.Title,
                Content = resource.Content,
                ParentId = resource.ParentId,
                SystemId = resource.SystemId,
                Description = resource.Description,
                ResourceType = resource.ResourceType,
                IdentityType = resource.IdentityType,
                OwnerId = resource.OwnerId,
                LevelId = resource.LevelId,
                AccessLevel = resource.AccessLevel,
                HasPermission = resource.HasPermission,
                //TODO 记录当前资源的访问规则会增加系统处理量, 但使用率不高
                AccessRules = currentAccessRules
            });
        }

        return resources.OrderBy(x => x.Code).ToList();
    }
    /// <summary>
    /// 对用户可访问的资源树进行剪枝操作
    /// </summary>
    /// <param name="source">用户有权限的所有资源原数据</param>
    /// <param name="target">当前权限节点</param>
    /// <returns></returns>
    private static void InitUserResourceTree(List<ResourceViewModel> source, ResourceViewModel target)
    {
        var children = source.Where(x => x.ParentId == target.Id).ToList();
        if (children.Count != 0)
        {
            target.Children = [];
            foreach (var child in children)
            {
                target.Children.Add(child);
                InitUserResourceTree(source, child);
            }
        }
    }
    /// <summary>
    /// 获取授权资源, 未获取到则没有得到授权
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="resource">授权资源</param>
    /// <returns></returns>
    private ResourceViewModel? GetPermissionResource(UserContext userContext, string? resource = default)
    {
        var system = string.Empty;
        var resourceContent = resource;
        ResourceViewModel? viewModel = null;

        if (!string.IsNullOrWhiteSpace(resource))
        {
            //cond 1. 直接传值的情况
            var uri = new Uri(resource);
            system = uri.Host;
            resourceContent = uri.PathAndQuery.ToLowerInvariant();
        }
        else
        {
            //cond 2. 判断 request path 的情况
            system = _request.Host.ToString();
            resourceContent = _request.Path.ToString().ToLowerInvariant();
        }

        viewModel = GetMatchedResource(userContext, system, resourceContent);
        if (viewModel is not null)
        {
            _logger.LogInformation($"get {userContext.UserName} user permission for system {system} and resource {resource} success in condition 1 & 2!");
            return viewModel;
        }

        //cond 3. 判断 /api/controller/action 的情况
        var (path, action) = GetRequestPath();
        viewModel = GetMatchedResource(userContext, system, path);
        if (viewModel is not null)
        {
            _logger.LogInformation($"get {userContext.UserName} user permission for route path {path} success in condition 3!");
            return viewModel;
        }

        //cond 4. 路由参数存在的情况下, 判断 request path 路由变量替换值和删除路由变量值的情况
        if (_routeValues.Count > 2)
        {
            var path1 = path;
            var path2 = resourceContent;
            var path3 = resourceContent;
            foreach (var route in _routeValues.Where(x => x.Key != CONTROLLER && x.Key != ACTION))
            {
                var routeValue = AppSettings.CHAR_SLASH + (route.Value?.ToString()?.ToLowerInvariant() ?? string.Empty);
                path1 = path1.Replace(routeValue, AppSettings.CHAR_SLASH + "{" + route.Key.ToLowerInvariant() + "}");
                path2 = path2.Replace(routeValue, AppSettings.CHAR_SLASH + "{" + route.Key.ToLowerInvariant() + "}");
                path3 = path3.Replace(routeValue, string.Empty);
            }

            viewModel = GetMatchedResource(userContext, system, path1);
            if (viewModel is not null)
            {
                _logger.LogInformation($"get {userContext.UserName} user permission for request path and route params {path1} success in condition 4.1!");
                return viewModel;
            }

            viewModel = GetMatchedResource(userContext, system, path2);
            if (viewModel is not null)
            {
                _logger.LogInformation($"get {userContext.UserName} user permission for request path and route params {path2} success in condition 4.2!");
                return viewModel;
            }

            viewModel = GetMatchedResource(userContext, system, path3);
            if (viewModel is not null)
            {
                _logger.LogInformation($"get {userContext.UserName} user permission for request path and route params {path3} success in condition 4.3!");
                return viewModel;
            }
        }

        //cond 5. 判断 request path 去掉 http method 部分的情况, 因为 path 中可以缺省 action 的部分
        if (action == _request.Method.ToLowerInvariant())
        {
            var method = AppSettings.CHAR_SLASH + action + AppSettings.CHAR_SLASH;
            if (path.Contains(method))
            {
                path = path.Replace(method, string.Empty);
                viewModel = GetMatchedResource(userContext, system, path);
                if (viewModel is not null)
                {
                    _logger.LogInformation($"get {userContext.UserName} user permission for request path without http method and / {path} success in condition 5.11!");
                    return viewModel;
                }
            }

            method = AppSettings.CHAR_SLASH + action;
            if (path.EndsWith(method))
            {
                path = path.Replace(method, string.Empty);
                viewModel = GetMatchedResource(userContext, system, path);
                if (viewModel is not null)
                {
                    _logger.LogInformation($"get {userContext.UserName} user permission for request path without http method {path} success in condition 5.2!");
                    return viewModel;
                }
            }
        }

        _logger.LogInformation($"get {userContext.UserName} user permission for request resource {resource ?? path} in all situation falied!");
        return null;
    }

    /// <summary>
    /// 根据用户上下文, 系统和资源访问地址获取当前匹配的资源
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="system">当前系统</param>
    /// <param name="resource">当前资源访问地址</param>
    /// <returns></returns>
    private ResourceViewModel? GetMatchedResource(UserContext userContext, string system, string resource)
    {
        if (userContext.Resources.ContainsKey(system))
        {
            var keyValuePairs = userContext.Resources[system];
            if (keyValuePairs.ContainsKey(resource))
            {
                return keyValuePairs[resource];
            }
        }

        return default;
    }

    /// <summary>
    /// 访问规则校验
    /// 验证并记录访问次数
    /// 
    /// 验证访问规则条件, 条件授权需要全部满足, 不像 IsAllow 只需最高优先级的满足即可
    /// 目前支持的规则包括两种
    /// 1. @method params..., @ 开头的参数为调用内部方法进行访问授权, 方法的第一个参数不需要填写, 均为 UserContext
    /// 2. {field} operator value, 其中 {field} 为路由参数, 用户上下文的参数, 和来自 http 上下文参数
    /// 
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="viewModel">当前资源</param>
    /// <returns></returns>
    private async Task<bool> AuthorizeAccessRuleAsync(UserContext userContext, ResourceViewModel viewModel)
    {
        //同一个资源同一个用户非授权类型只会存在唯一一条记录
        var accessRecord = await _context.AccessRecords.FirstOrDefaultAsync(x => x.UserId == userContext.UserId && x.ResourceId == viewModel.Id && x.AccessRuleId == null);
        if (accessRecord == null)
        {
            accessRecord = new AccessRecord
            {
                UserId = userContext.UserId,
                ResourceId = viewModel.Id
            };

            accessRecord.Create();
            await _context.AccessRecords.AddAsync(accessRecord);
            await _context.SaveChangesAsync();
        }

        var result = true;
        accessRecord.AccessCount++;

        if (viewModel.AccessRules is not null)
        {
            foreach (var accessRule in viewModel.AccessRules)
            {
                if (accessRule.AccessCount > 0 && accessRecord.AccessCount > accessRule.AccessCount)
                {
                    //当且仅当访问规则中最小的限制访问次数大于 0, 且访问次数已经大于限制访问次数, 将拒绝访问
                    _logger.LogInformation($"user {userContext.UserName} access resource {viewModel.Code} in access rule {accessRule.Code} has been accessed count {accessRecord.AccessCount} greater than limited count {accessRule.AccessCount}, will be denied!");
                    result = false;
                    break;
                }

                if (!string.IsNullOrWhiteSpace(accessRule.Condition) && accessRule.Code is not null)
                {
                    var conditions = accessRule.Condition.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                    for (int index = 0; index < conditions.Length; index++)
                    {
                        var condition = conditions[index];
                        var values = condition.Split(AppSettings.CHAR_SPACE, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                        var field = values[0];
                        if (field.StartsWith(AppSettings.CHAR_AT))
                        {
                            result = MatchMethod(userContext, accessRule.Code, condition, values);
                        }
                        else if (values.Length >= 3 && field.StartsWith('{') && field.EndsWith('}'))
                        {
                            result = MatchExpression(userContext, accessRule.Code, condition, values);
                        }

                        if (!result)
                        {
                            _logger.LogInformation($"user {userContext.UserName} access resource {viewModel.Code} not match the access rule {accessRule.Code} condition {condition}, will be denied!");
                            result = false;
                            break;
                        }

                        _logger.LogDebug($"user {userContext.UserName} access resource {viewModel.Code} in access rule {accessRule.Code} condition {condition} passed!");
                    }

                    _logger.LogInformation($"user {userContext.UserName} access resource {viewModel.Code} in access rule {accessRule.Code} condition {accessRule.Condition} all passed!");
                }
            }
        }

        if (result)
        {
            accessRecord.AllowedCount++;
            _logger.LogInformation($"user {userContext.UserName} access resource {viewModel.Code} all access rules conditions passed!");
        }

        _context.AccessRecords.Update(accessRecord);
        await _context.SaveChangesAsync();

        return result;
    }

    /// <summary>
    /// 获取资源访问路径, action
    /// 资源访问路径包含了路由参数变量名, 即: /api/controller/action
    /// </summary>
    /// <returns></returns>
    private (string, string) GetRequestPath()
    {
        //=controller
        var controller = string.Empty;
        if (_routeValues.TryGetValue(CONTROLLER, out object? controllerValue) && controllerValue is not null && controllerValue is string stringValue)
        {
            controller = stringValue;
        }

        //=action
        var action = string.Empty;
        if (_routeValues.TryGetValue(ACTION, out object? actionValue) && actionValue is not null && actionValue is string stringvalue)
        {
            action = stringvalue;
        }

        ArgumentNullException.ThrowIfNull(controller);
        ArgumentNullException.ThrowIfNull(action);

        //=/controller/action
        var _path = AppSettings.CHAR_SLASH + controller + AppSettings.CHAR_SLASH + action;

        if (_request.Path.HasValue)
        {
            var _prefix = string.Empty;
            var path = _request.Path.Value.ToLowerInvariant();
            var index = path.IndexOf(controller, StringComparison.InvariantCultureIgnoreCase);
            if (path != AppSettings.CHAR_SLASH.ToString() && index > 1)
            {
                //前缀, api/odata 之类
                _prefix = _request.Path.Value[1..(index - 1)];
            }

            if (_path != _request.Path.Value)
            {
                if (_prefix != null && _request.Path.Value != AppSettings.CHAR_SLASH + _prefix + _path)
                {
                    //=/api/controller/action
                    _path = AppSettings.CHAR_SLASH + _prefix + _path;
                }
                else
                {
                    _path = _request.Path.Value;
                }
            }
        }

        return (_path.ToLowerInvariant(), action.ToLowerInvariant());
    }

    /// <summary>
    /// @ 开头的参数为调用内部方法进行访问授权, 方法的第一个参数不需要填写, 均为 UserContext
    /// @method params...
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="accessRule">访问规则</param>
    /// <param name="condition">当前条件</param>
    /// <param name="values">条件参数</param>
    /// <returns></returns>
    private bool MatchMethod(UserContext userContext, string accessRule, string condition, params string[] values)
    {
        var method = values[0];
        _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from method: {method}");

        var methodInfo = GetType().GetMethod(method);
        if (methodInfo is not null)
        {
            var parameters = new object[] { userContext };
            if (values.Length > 1)
            {
                parameters.AddRange(values[1..]);
            }

            var returnValue = methodInfo.Invoke(this, parameters);
            if (returnValue != null && returnValue is bool result)
            {
                if (!result)
                {
                    _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from method: {method}, execute result not passed, access denied!");
                }

                return result;
            }
        }

        _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from method: {method}, method not found or params not matched, access denied!");
        return false;
    }
    /// <summary>
    /// 变量构成的表达式
    /// 结构: {field} operator value, 其中 {field} 为路由参数, 用户上下文的参数, 和来自 http 上下文的参数
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="accessRule">访问规则</param>
    /// <param name="condition">当前条件</param>
    /// <param name="values">条件参数</param>
    /// <returns></returns>
    private bool MatchExpression(UserContext userContext, string accessRule, string condition, params string[] values)
    {
        //字段
        var field = values[0];
        //逻辑
        var operatorType = values[1].GetEnumValueByJsonName<OperatorType>();
        //预设值
        var fieldValue = string.Concat(values[2..]);

        _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from variable: {field}, operator: {operatorType}, setting value: {fieldValue}");

        //实际值
        //默认规则形如: field eq value, 根据 field 从请求参数获取参数值, 和 value 进行比较以判断结果
        string? value;

        //{} 参数来自路由或上下文用户信息
        //如: /api/users/getUserRoles/{userName} 次数的 userName 参数值来自路由, 因此规则可以写为: {userName} contains administrator, developer, 表示仅限 administrator 和 developer 用户
        if (_routeValues.ContainsKey(field) && _routeValues.TryGetValue(field, out object? routeValue))
        {
            value = routeValue?.ToString();
            _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from route variable: {field}, operator: {operatorType}, setting value: {fieldValue}, actual value: {value}");
        }
        else
        {
            //1. UserContext value
            value = userContext.GetPropertyValue<UserContext, string>(field);

            //2. HttpContext query option
            value ??= _httpContext.GetContextOption<string>(field);

            //3. HttpRequest value
            value ??= _request.GetPropertyValue<HttpRequest, string>(field);

            //4. HttpContext value
            value ??= _httpContext.GetPropertyValue<HttpContext, string>(field);

            _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from context variable: {field}, operator: {operatorType}, setting value: {fieldValue}, actual value: {value}");
        }

        value ??= string.Empty;

        //比较规则为: 实际值和预设值比较, 如果比较结果为 false, 则表示不满足条件
        var result = operatorType switch
        {
            OperatorType.Equal => value == fieldValue,
            OperatorType.NotEqual => value != fieldValue,
            OperatorType.GreaterThan => Convert.ToDecimal(value) > Convert.ToDecimal(fieldValue),
            OperatorType.GreaterThanAndEqual => Convert.ToDecimal(value) >= Convert.ToDecimal(fieldValue),
            OperatorType.LessThan => Convert.ToDecimal(value) < Convert.ToDecimal(fieldValue),
            OperatorType.LessThanAndEqual => Convert.ToDecimal(value) >= Convert.ToDecimal(fieldValue),
            OperatorType.Contains => value.Contains(fieldValue),
            OperatorType.StartsWith => value.StartsWith(fieldValue),
            OperatorType.EndsWith => value.EndsWith(fieldValue),
            OperatorType.In => fieldValue.Contains(value),
            OperatorType.NotIn => !fieldValue.Contains(value),
            _ => false
        };

        if (!result)
        {
            _logger.LogDebug($"user {userContext.UserName} access rule: {accessRule} condition: {condition} from variable: {field}, operator: {operatorType}, setting value: {fieldValue}, actual value: {value}, check result not passed, access denied!");
        }

        return result;
    }

    /// <summary>
    /// odata 查询表达式不允许出现 select 的 value 值
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="value">select 表达式预设值，为逗号分隔的字符串，表示不可被查询的列名，默认空, 或 *, 表示必须提供 select 选项, 不可以查询全部列</param>
    /// <returns></returns>
    private bool ODataNotAllowSelect(UserContext userContext, string? value = default)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: ODataNotAllowSelect (OData protocol not allow $select expression), parameter: {value}");

        //验证实际值
        var select = _request.GetRequestOption<string>(AppSettings.QUERY_OPTION_SELECT);
        if (string.IsNullOrWhiteSpace(select) || select == AppSettings.CHAR_STAR.ToString())
        {
            return false;
        }

        //验证预设值
        if (string.IsNullOrWhiteSpace(value) || value == AppSettings.CHAR_STAR.ToString())
        {
            //当预设值为全部列，意味实际值只要不为全部列即可
            return (select is not null) && select != AppSettings.CHAR_STAR.ToString();
        }

        //验证实际值其余情况
        return !select.ContainsAny(value, AppSettings.CHAR_COMMA);
    }
    /// <summary>
    /// odata 查询表达式不允许出现 expand 的 value 值
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="value">expand 表达式的预设值, 表示不能出现在 expand 中的选项</param>
    /// <returns></returns>
    private bool ODataNotAllowExpand(UserContext userContext, string? value = default)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: ODataNotAllowExpand (OData protocol not allow $expand expression), parameter: {value}");
        if (string.IsNullOrWhiteSpace(value))
        {
            return true;
        }

        var expand = _request.GetRequestOption<string>(AppSettings.QUERY_OPTION_EXPAND);
        if (!string.IsNullOrWhiteSpace(expand))
        {
            return !expand.ContainsAny(value, AppSettings.CHAR_COMMA);
        }

        return true;
    }
    /// <summary>
    /// 用户是否分配了某角色
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="roles">角色</param>
    /// <returns></returns>
    private bool UserInRoles(UserContext userContext, params string[] roles)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: UserInRoles (whether the user is assigned roles: {roles.ToString(AppSettings.DEFAULT_OUTER_SPLITER)})");
        return userContext.Roles?.Select(x => x.Name?.ToUpperInvariant())?.Intersect(roles.Select(x => x.ToUpperInvariant())).Any() ?? false;
    }
    /// <summary>
    /// 用户是否分配了某群组
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="groups">群组</param>
    /// <returns></returns>
    private bool UserInGroups(UserContext userContext, params string[] groups)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: UserInGroups (whether the user is assigned groups: {groups.ToString(AppSettings.DEFAULT_OUTER_SPLITER)})");
        return userContext.Groups?.Select(x => x.Name?.ToUpperInvariant())?.Intersect(groups.Select(x => x.ToUpperInvariant())).Any() ?? false;
    }
    /// <summary>
    /// 请求需匹配具体的 Http Method, 不区分大小写
    /// </summary>
    /// <param name="userContext">用户上下文</param>
    /// <param name="httpMethod">Http Method</param>
    /// <returns></returns>
    private bool HttpMethod(UserContext userContext, string httpMethod)
    {
        _logger.LogDebug($"user {userContext.UserName} execute access rule: HttpMethod (whether the request http method is matched: {httpMethod})");
        return httpMethod.Contains(_request.Method, StringComparison.InvariantCultureIgnoreCase);
    }
    #endregion
}